﻿using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using IdentityServer4.EntityFramework.Entities;
using IdentityServer4.EntityFramework.Mappers;
using K9Nano.Admin.Data;
using K9Nano.Dependency;
using K9Nano.Share.Exceptions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using ApiScope = IdentityServer4.Models.ApiScope;

namespace K9Nano.Admin.Identity
{
    public class ApiScopeAppService : IApiScopeAppService, IScopedDependency
    {
        protected readonly K9ConfigurationDbContext DbContext;

        public ApiScopeAppService(K9ConfigurationDbContext dbContext)
        {
            DbContext = dbContext;
        }

        public async ValueTask<IReadOnlyList<ApiScope>> GetAllAsync(CancellationToken cancellation)
        {
            var scopes = await DbContext.ApiScopes.Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .AsNoTracking()
                .ToListAsync(cancellation);
            return scopes.Select(x => x.ToModel()).ToList();
        }

        public async ValueTask<ApiScope> GetByNameAsync(string name, CancellationToken cancellation)
        {
            var scope = await DbContext.ApiScopes.Include(x => x.UserClaims)
                .Include(x => x.Properties)
                .AsNoTracking()
                .FirstOrDefaultAsync(x => x.Name == name, cancellation);
            return scope.ToModel();
        }

        public async ValueTask AddOrUpdateAsync(ApiScope scope, CancellationToken cancellation)
        {
            if (string.IsNullOrEmpty(scope.Name))
            {
                throw new K9UserFriendlyException("Name 不能为空");
            }

            var baseQuery = DbContext.ApiScopes.Where(x => x.Name == scope.Name);

            var entity = (await baseQuery.ToArrayAsync(cancellation)).SingleOrDefault();

            if (entity == null)
            {
                entity = scope.ToEntity();
                await DbContext.ApiScopes.AddAsync(entity, cancellation);
                await DbContext.SaveChangesAsync(cancellation);
                return;
            }

            entity.Enabled = scope.Enabled;
            entity.Name = scope.Name;
            entity.DisplayName = scope.DisplayName;
            entity.Description = scope.Description;
            entity.ShowInDiscoveryDocument = scope.ShowInDiscoveryDocument;
            await baseQuery.Include(x => x.UserClaims).SelectMany(x => x.UserClaims).LoadAsync(cancellation);
            await baseQuery.Include(x => x.Properties).SelectMany(x => x.Properties).LoadAsync(cancellation);
            entity.UserClaims.Clear();
            entity.Properties.Clear();
            IDbContextTransaction tran = null;

            try
            {
                tran = await DbContext.Database.BeginTransactionAsync(cancellation);
                await DbContext.SaveChangesAsync(cancellation);

                entity.UserClaims = scope.UserClaims
                    .Select(x => new ApiScopeClaim
                    {
                        Type = x
                    })
                    .ToList();
                entity.Properties = scope.Properties.Select(x => new ApiScopeProperty
                {
                    Key = x.Key,
                    Value = x.Value
                }).ToList();

                await DbContext.SaveChangesAsync(cancellation);
                await tran.CommitAsync(cancellation);
            }
            catch
            {
                await tran.RollbackAsync();
                throw;
            }
            finally
            {
                tran?.Dispose();
            }
        }

        public async ValueTask DeleteAsync(string name, CancellationToken cancellation)
        {
            var entity = await DbContext.ApiScopes
                .FirstOrDefaultAsync(x => x.Name == name, cancellation);

            if (entity != null)
            {
                DbContext.ApiScopes.Remove(entity);
                await DbContext.SaveChangesAsync(cancellation);
            }
        }

        public async ValueTask SetEnableAsync(string name, bool enable, CancellationToken cancellation)
        {
            var entity = await DbContext.ApiScopes
                .FirstOrDefaultAsync(x => x.Name == name, cancellation);

            if (entity != null && entity.Enabled != enable)
            {
                entity.Enabled = enable;
                await DbContext.SaveChangesAsync(cancellation);
            }
        }
    }
}